using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;

namespace Hsenl.Network {
    public abstract class Sender : EpibolySocket, ISender {
        public int TcpSendBufferSizePer { get; set; } = 1024;
        public int UdpSendBufferSizePer { get; set; } = 1500;

        private volatile int _isSending;
        private SocketBufferKind _bufferKind; // 有时候如果对于memory支持不友好的环境时, 可以使用array. 例如unity中, 对于memory的支持就不友好, 所以这只是权宜之计, 才增加了对byte[]的支持.

        public bool IsSending => this._isSending != 0;
        public SocketBufferKind BufferKind => this._bufferKind;

        protected Sender(Socket socket, SocketBufferKind bufferKind) : base(socket) {
            this.Init(socket, bufferKind);
        }

        public void Init(Socket socket, SocketBufferKind bufferKind) {
            base.Init(socket);
            this._bufferKind = bufferKind;
        }

        // tcp
        protected override bool SendAsync(SocketFlags socketFlags = SocketFlags.None) {
            this.CheckDisposedException();

            if (this.CompareExchange()) {
                this.SendAsync_Private().Tail();
                return true;
            }

            return false;
        }

        // tcp
        // 适合需要高效发送多个缓冲区或文件数据的场景
        protected override bool SendPacketsAsync(IList<ArraySegment<byte>> buffers, SocketFlags socketFlags = SocketFlags.None) {
            this.CheckDisposedException();

            if (this.CompareExchange()) {
                this.SendPacketsAsync_Private(buffers);
                return true;
            }

            return false;
        }

        // udp(发送必须指定一个地址)
        protected override bool SendToAsync(SocketFlags socketFlags = SocketFlags.None) {
            this.CheckDisposedException();

            if (this.CompareExchange()) {
                this.SendToAsync_Private().Tail();
                return true;
            }

            return false;
        }

        // 发送数据
        private async hvoid SendAsync_Private() {
            try {
                while (true) {
                    if (this.IsClosedOrDisposed()) {
                        return;
                    }

                    try {
                        switch (this.BufferKind) {
                            case SocketBufferKind.Memory: {
                                var memory = this.GetSendBuffer(this.TcpSendBufferSizePer);
                                if (memory.Length == 0) {
                                    this.StopSending();
                                    return;
                                }

                                var len = await this.Socket.SendAsync(memory);
                                this.OnSend(memory.Slice(0, len));
                                break;
                            }
                            case SocketBufferKind.Array: {
                                var bytes = this.GetSendBuffer(this.TcpSendBufferSizePer, out var offset, out var count);
                                if (bytes.Length == 0) {
                                    this.StopSending();
                                    return;
                                }

                                var len = await this.Socket.SendAsync(bytes.AsMemory(offset, count));
                                this.OnSend(bytes, offset, len);
                                break;
                            }
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                    }
                    catch (SocketException exception) {
                        this.Error(exception.ErrorCode);
                    }
                    catch (Exception exception) {
                        Log.Error(exception);
                    }
                }
            }
            finally {
                this.StopSending();
            }
        }

        // 发送数据packets
        private async hvoid SendPacketsAsync_Private(IList<ArraySegment<byte>> seg) {
            try {
                if (this.IsClosedOrDisposed()) {
                    return;
                }

                try {
                    await this.Socket.SendAsync(seg);
                }
                catch (SocketException exception) {
                    this.Error(exception.ErrorCode);
                }
                catch (Exception exception) {
                    Log.Error(exception);
                }
            }
            finally {
                this.StopSending();
            }
        }

        // 发送数据to
        private async hvoid SendToAsync_Private() {
            try {
                while (true) {
                    if (this.IsClosedOrDisposed()) {
                        return;
                    }

                    try {
                        switch (this.BufferKind) {
                            case SocketBufferKind.Memory: {
                                var memory = this.GetSendToBuffer(this.UdpSendBufferSizePer, out var remoteEndPoint);
                                if (memory.Length == 0) {
                                    this.StopSending();
                                    return;
                                }

                                var len = await this.Socket.SendToAsync(memory, remoteEndPoint);
                                this.OnSendTo(remoteEndPoint, memory.Slice(0, len));
                                break;
                            }
                            case SocketBufferKind.Array: {
                                var bytes = this.GetSendToBuffer(this.UdpSendBufferSizePer, out var offset, out var count, out var remoteEndPoint);
                                if (bytes.Length == 0) {
                                    this.StopSending();
                                    return;
                                }

                                var len = await this.Socket.SendToAsync(bytes.AsMemory(offset, count), remoteEndPoint);
                                this.OnSendTo(remoteEndPoint, bytes, offset, len);
                                break;
                            }
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                    }
                    catch (SocketException exception) {
                        this.Error(exception.ErrorCode);
                    }
                    catch (Exception exception) {
                        Log.Error(exception);
                    }
                }
            }
            finally {
                this.StopSending();
            }
        }

        protected abstract Memory<byte> GetSendBuffer(int length);
        protected abstract Memory<byte> GetSendToBuffer(int length, out EndPoint remoteEndPoint);
        protected abstract byte[] GetSendBuffer(int length, out int offset, out int count);
        protected abstract byte[] GetSendToBuffer(int length, out int offset, out int count, out EndPoint remoteEndPoint);

        protected virtual void OnSend(Memory<byte> data) { }
        protected virtual void OnSend(byte[] data, int offset, int count) { }

        protected virtual void OnSendTo(EndPoint remoteEndPoint, Memory<byte> data) { }
        protected virtual void OnSendTo(EndPoint remoteEndPoint, byte[] data, int offset, int count) { }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool CompareExchange() {
            return Interlocked.CompareExchange(ref this._isSending, 1, 0) == 0;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void StopSending() {
            Interlocked.Exchange(ref this._isSending, 0);
            // 我们无法直接的关闭 saea, 需要等到他 completed, 才算是关闭了
            if (this.IsClosed) {
                this.OnClosed();
            }
        }

        public override void Close(bool closeSocket = true) {
            if (this.IsClosed)
                return;

            base.Close(closeSocket);

            if (!this.IsSending) {
                this.OnClosed();
            }
        }

        public override void Dispose() {
            if (this.IsDisposed)
                return;

            base.Dispose();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private bool IsClosedOrDisposed() {
            return this.IsClosed || this.IsDisposed;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void CheckGreaterThanTcpSendSizeException(int len) {
            if (len > this.TcpSendBufferSizePer)
                throw new ArgumentOutOfRangeException($"SendBufferLength over maximum '{len}' '{this.TcpSendBufferSizePer}'");
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void CheckGreaterThanUdpSendSizeException(int len) {
            if (len > this.UdpSendBufferSizePer)
                throw new ArgumentOutOfRangeException($"SendBufferLength over maximum '{len}' '{this.UdpSendBufferSizePer}'");
        }
    }
}